import os
import sys
import csv
import shutil
import time
from chardet import detect
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
from difflib import SequenceMatcher
from datetime import datetime

class FileRenamerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("文件批量重命名工具")
        
        # 设置窗口大小并居中
        window_width = 1000
        window_height = 700
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")
        
        # 配置主窗口的网格权重，使其可以自适应调整
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)
        
        # 创建主框架
        self.main_frame = ttk.Frame(root, padding="10")
        self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置主框架的网格权重
        self.main_frame.grid_rowconfigure(2, weight=1)  # 预览区域所在行
        self.main_frame.grid_columnconfigure(0, weight=1)
        
        self.backup_folder = None  # 修改默认备份目录为源文件夹下的backups子文件夹
        self.create_menu()
        self.create_input_area()
        self.create_preview_area()
        self.create_status_bar()
        
    def create_menu(self):
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)
        
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="选择文件夹", command=self.select_folder)
        file_menu.add_command(label="选择文本文件", command=self.select_text_file)
        file_menu.add_command(label="设置备份目录", command=self.select_backup_folder)
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.root.quit)
        
        # 编辑菜单
        edit_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        edit_menu.add_command(label="撤销重命名", command=self.undo_rename)
        edit_menu.add_command(label="重置预览", command=self.reset_preview)
        edit_menu.add_separator()
        edit_menu.add_command(label="设置", command=self.show_settings)
        
        # 视图菜单
        view_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="视图", menu=view_menu)
        view_menu.add_command(label="刷新", command=self.refresh_preview)
        view_menu.add_command(label="显示文件详情", command=self.show_file_details)
        
        # 工具菜单
        tools_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="工具", menu=tools_menu)
        tools_menu.add_command(label="自定义格式重命名", command=self.custom_format)
        tools_menu.add_command(label="导出重命名记录", command=self.export_rename_log)
        
        # 帮助菜单
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="帮助", menu=help_menu)
        help_menu.add_command(label="使用说明", command=self.show_help)
        help_menu.add_command(label="关于", command=self.show_about)
        
    def create_input_area(self):
        # 文件夹选择
        input_frame = ttk.Frame(self.main_frame)
        input_frame.grid(row=0, column=0, columnspan=3, sticky=(tk.W, tk.E))
        
        ttk.Label(input_frame, text="源文件夹:").grid(row=0, column=0, sticky=tk.W)
        self.folder_path = tk.StringVar()
        ttk.Entry(input_frame, textvariable=self.folder_path, width=100).grid(row=0, column=1, sticky=tk.W)
        ttk.Button(input_frame, text="浏览", command=self.select_folder).grid(row=0, column=2, padx=5)
        
        # 文本文件选择
        ttk.Label(input_frame, text="名称文件:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.text_file_path = tk.StringVar()
        ttk.Entry(input_frame, textvariable=self.text_file_path, width=100).grid(row=1, column=1, sticky=tk.W, pady=5)
        ttk.Button(input_frame, text="浏览", command=self.select_text_file).grid(row=1, column=2, padx=5, pady=5)
        
        # 操作按钮
        btn_frame = ttk.Frame(self.main_frame)
        btn_frame.grid(row=1, column=0, columnspan=3, pady=10)
        ttk.Button(btn_frame, text="预览匹配", command=self.preview_matching).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="开始重命名", command=self.start_renaming).pack(side=tk.LEFT, padx=5)
        
        # 预览区域
        preview_frame = ttk.LabelFrame(self.main_frame, text="预览区域", padding="5")
        preview_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        
        # 添加更多控制选项
        options_frame = ttk.LabelFrame(self.main_frame, text="选项", padding="5")
        options_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        # 添加选项
        self.create_backup_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(options_frame, text="创建备份", variable=self.create_backup_var).pack(side=tk.LEFT, padx=5)

    def create_preview_area(self):
        # 改用主预览区域的frame
        preview_frame = self.main_frame.winfo_children()[2]
        
        # 配置预览框架的网格权重
        preview_frame.grid_columnconfigure(0, weight=1)
        preview_frame.grid_rowconfigure(0, weight=1)
        
        # 添加预览列表
        columns = ("原文件名", "新文件名", "匹配度", "状态")
        self.preview_tree = ttk.Treeview(preview_frame, columns=columns, show="headings", height=15)
        
        # 设置各列的宽度和标题
        self.preview_tree.heading("原文件名", text="原文件名")
        self.preview_tree.heading("新文件名", text="新文件名")
        self.preview_tree.heading("匹配度", text="匹配度")
        self.preview_tree.heading("状态", text="状态")
        
        self.preview_tree.column("原文件名", width=300)
        self.preview_tree.column("新文件名", width=300)
        self.preview_tree.column("匹配度", width=100)
        self.preview_tree.column("状态", width=100)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(preview_frame, orient=tk.VERTICAL, command=self.preview_tree.yview)
        self.preview_tree.configure(yscrollcommand=scrollbar.set)
        
        # 调整预览树和滚动条的布局
        self.preview_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
    def create_status_bar(self):
        self.status_var = tk.StringVar()
        self.status_bar = ttk.Label(self.main_frame, textvariable=self.status_var)
        self.status_bar.grid(row=4, column=0, columnspan=3, sticky=tk.W)

    def select_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            self.folder_path.set(folder)
            self.status_var.set(f"已选择文件夹: {folder}")
            
    def select_text_file(self):
        filename = filedialog.askopenfilename(filetypes=[("CSV数据表", "*.csv"), ("所有文件", "*.*")])
        if filename:
            self.text_file_path.set(filename)
            self.status_var.set(f"已选择文本文件: {filename}")

    def select_backup_folder(self):
        folder = filedialog.askdirectory(title="选择备份目录")
        if folder:
            self.backup_folder = folder
            self.status_var.set(f"已设置备份目录: {folder}")
            
    def get_similarity(self, str1: str, str2: str) -> float:
        """计算两个字符串的相似度"""
        return SequenceMatcher(None, str1.lower(), str2.lower()).ratio()

    def find_best_match(self, filename: str, new_names: list) -> tuple:
        """找到最佳匹配的新名称"""
        name_without_ext = os.path.splitext(filename)[0]
        best_match = None
        best_ratio = 0
        
        for new_name in new_names:
            ratio = self.get_similarity(name_without_ext, new_name)
            if ratio > best_ratio:
                best_ratio = ratio
                best_match = new_name
                
        return best_match, best_ratio

    def preview_matching(self):
        try:
            folder = self.folder_path.get()
            text_file = self.text_file_path.get()
            
            if not folder or not text_file:
                messagebox.showwarning("警告", "请先选择文件夹和文本文件")
                return
                
            # 清空预览列表
            for item in self.preview_tree.get_children():
                self.preview_tree.delete(item)
                
            # 检测文件编码
            with open(text_file, 'rb') as f:
                raw_data = f.read()
                detected = detect(raw_data)
                encoding = detected['encoding']
            
            # 读取文本文件中的对照数据
            name_mapping = {}  # 用于存储 {匹配名: 新名称} 的字典
            try:
                with open(text_file, 'r', encoding=encoding) as f:
                    csv_reader = csv.reader(f)
                    for row in csv_reader:
                        if len(row) >= 2:  # 确保至少有两列
                            new_name, match_name = row[0], row[1].strip()
                            name_mapping[match_name] = new_name
            except UnicodeDecodeError:
                # 如果检测到的编码不正确，尝试其他常见编码
                encodings = ['gb2312', 'gbk', 'gb18030', 'utf-8', 'utf-16']
                for enc in encodings:
                    try:
                        with open(text_file, 'r', encoding=enc) as f:
                            csv_reader = csv.reader(f)
                            for row in csv_reader:
                                if len(row) >= 2:
                                    new_name, match_name = row[0], row[1].strip()
                                    name_mapping[match_name] = new_name
                        encoding = enc  # 找到正确的编码
                        break
                    except UnicodeDecodeError:
                        continue
                
            if not name_mapping:
                raise Exception("无法正确读取文件，请检查文件编码格式")
                
            # 获取文件夹中的文件
            files = os.listdir(folder)
            
            # 精确匹配并显示预览
            for old_name in files:
                name_without_ext = os.path.splitext(old_name)[0]
                ext = os.path.splitext(old_name)[1]
                
                # 在映射表中查找匹配
                new_name = name_mapping.get(name_without_ext, "")
                if new_name:
                    new_filename = new_name + ext
                    status = "匹配成功"
                    ratio = "100%"  # 精确匹配为100%
                else:
                    new_filename = ""
                    status = "未找到匹配"
                    ratio = "0%"
                    
                self.preview_tree.insert("", tk.END, values=(
                    old_name,
                    new_filename,
                    ratio,
                    status
                ))
                    
            self.status_var.set("预览完成")
            
        except Exception as e:
            messagebox.showerror("错误", f"文件读取错误: {str(e)}\n当前尝试使用的编码: {encoding}")
            self.status_var.set("预览失败")
            
    def start_renaming(self):
        try:
            if not self.preview_tree.get_children():
                messagebox.showwarning("警告", "请先进行预览")
                return
                
            if not messagebox.askyesno("确认", "确定要开始重命名吗？此操作不可撤销。"):
                return
                
            folder = self.folder_path.get()
            success_count = 0
            fail_count = 0  # 添加未重命名计数
            
            # 修改备份逻辑
            if self.create_backup_var.get():
                # 使用源文件夹下的backups目录作为默认备份位置
                source_folder = self.folder_path.get()
                if not self.backup_folder:
                    self.backup_folder = os.path.join(source_folder, "backups")
                
                # 确保备份目录存在
                os.makedirs(self.backup_folder, exist_ok=True)
                
                # 创建带时间戳的备份子目录
                backup_time = time.strftime("%Y%m%d_%H%M%S")
                backup_dir = os.path.join(self.backup_folder, f"backup_{backup_time}")
                os.makedirs(backup_dir, exist_ok=True)
                
                # 创建备份
                for item in self.preview_tree.get_children():
                    values = self.preview_tree.item(item)['values']
                    old_name = values[0]
                    old_path = os.path.join(source_folder, old_name)
                    shutil.copy2(old_path, os.path.join(backup_dir, old_name))
            
            self.last_rename_actions = []
            
            for item in self.preview_tree.get_children():
                values = self.preview_tree.item(item)['values']
                old_name, new_name, _, status = values
                
                if status == "匹配成功" and new_name:
                    old_path = os.path.join(folder, old_name)
                    new_path = os.path.join(folder, new_name)
                    
                    os.rename(old_path, new_path)
                    self.last_rename_actions.append((new_name, old_name))
                    success_count += 1
                else:
                    fail_count += 1  # 统计未重命名的文件
                    
            # 修改完成提示，显示详细信息
            result_message = f"重命名完成！\n\n成功重命名: {success_count} 个文件\n未能重命名: {fail_count} 个文件\n总计: {success_count + fail_count} 个文件"
            messagebox.showinfo("完成", result_message)
            self.status_var.set(f"重命名完成，成功: {success_count}，失败: {fail_count}")
            
        except Exception as e:
            messagebox.showerror("错误", str(e))
            self.status_var.set("重命名失败")
            
    def undo_rename(self):
        if hasattr(self, 'last_rename_actions'):
            if messagebox.askyesno("确认", "确定要撤销上次的重命名操作吗？"):
                try:
                    for new_name, old_name in reversed(self.last_rename_actions):
                        folder = self.folder_path.get()
                        old_path = os.path.join(folder, old_name)
                        new_path = os.path.join(folder, new_name)
                        if os.path.exists(new_path):
                            os.rename(new_path, old_path)
                    messagebox.showinfo("成功", "撤销完成")
                    self.refresh_preview()
                except Exception as e:
                    messagebox.showerror("错误", f"撤销失败: {str(e)}")
        else:
            messagebox.showinfo("提示", "没有可撤销的重命名操作")

    def custom_format(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("自定义格式重命名")
        dialog.geometry("400x200")
        
        ttk.Label(dialog, text="格式示例: {name}_{num:03d}").pack(pady=5)
        format_entry = ttk.Entry(dialog, width=40)
        format_entry.pack(pady=5)
        format_entry.insert(0, "{name}_{num:03d}")
        
        def apply_format():
            format_str = format_entry.get()
            try:
                start_num = 1
                for item in self.preview_tree.get_children():
                    values = self.preview_tree.item(item)['values']
                    old_name = values[0]
                    name_without_ext = os.path.splitext(old_name)[0]
                    ext = os.path.splitext(old_name)[1]
                    new_name = format_str.format(
                        name=name_without_ext,
                        num=start_num,
                        ext=ext
                    ) + ext
                    self.preview_tree.set(item, "新文件名", new_name)
                    start_num += 1
                dialog.destroy()
            except Exception as e:
                messagebox.showerror("错误", f"格式错误: {str(e)}")
        
        ttk.Button(dialog, text="应用", command=apply_format).pack(pady=10)

    def export_rename_log(self):
        if not self.preview_tree.get_children():
            messagebox.showwarning("警告", "没有可导出的重命名记录")
            return
            
        filename = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
        )
        if filename:
            with open(filename, 'w', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                writer.writerow(["原文件名", "新文件名", "匹配度", "状态"])
                for item in self.preview_tree.get_children():
                    writer.writerow(self.preview_tree.item(item)['values'])
            messagebox.showinfo("成功", f"日志已导出到: {filename}")

    def show_file_details(self):
        if not self.preview_tree.get_children():
            messagebox.showwarning("警告", "没有文件可显示")
            return
            
        details_window = tk.Toplevel(self.root)
        details_window.title("文件详情")
        details_window.geometry("600x400")
        
        text_area = ScrolledText(details_window)
        text_area.pack(fill=tk.BOTH, expand=True)
        
        folder = self.folder_path.get()
        for item in self.preview_tree.get_children():
            values = self.preview_tree.item(item)['values']
            old_name = values[0]
            full_path = os.path.join(folder, old_name)
            try:
                stats = os.stat(full_path)
                text_area.insert(tk.END, f"文件: {old_name}\n")
                text_area.insert(tk.END, f"大小: {stats.st_size:,} 字节\n")
                text_area.insert(tk.END, f"创建时间: {os.path.getctime(full_path)}\n")
                text_area.insert(tk.END, f"修改时间: {os.path.getmtime(full_path)}\n")
                text_area.insert(tk.END, "-" * 50 + "\n")
            except Exception as e:
                text_area.insert(tk.END, f"无法获取 {old_name} 的详情: {str(e)}\n")

    def show_about(self):
        about_text = """文件批量重命名工具 v1.0
        
功能特点：
- 精确匹配
- 支持文件备份和撤销
- 支持自定义格式重命名
- 支持导出重命名记录

作者: 积极的风筝（堌阳1中）
联系: 564913478@qq.com
"""
        messagebox.showinfo("关于", about_text)

    def show_help(self):
        help_text = """使用说明：
1. 选择包含要重命名文件的文件夹
2. 选择包含新文件名的文本文件（CSV格式）
    注意：文件内只能有两列
        第一列为新文件名（准考证号）
        第二列为匹配名（即照片的名字身份证号码）
3. 点击"预览匹配"查看匹配结果
4. 确认无误后点击"开始重命名"
        """
        messagebox.showinfo("使用说明", help_text)

    def reset_preview(self):
        """重置预览区域"""
        if self.preview_tree.get_children():
            if messagebox.askyesno("确认", "确定要清空预览区域吗？"):
                for item in self.preview_tree.get_children():
                    self.preview_tree.delete(item)
                self.status_var.set("预览已重置")

    def refresh_preview(self):
        """刷新预览"""
        try:
            if self.folder_path.get() and self.text_file_path.get():
                self.preview_matching()
            else:
                messagebox.showinfo("提示", "请先选择文件夹和文本文件")
        except Exception as e:
            messagebox.showerror("错误", str(e))

    def show_settings(self):
        """显示设置对话框"""
        settings_window = tk.Toplevel(self.root)
        settings_window.title("设置")
        settings_window.geometry("400x300")
        
        # 创建设置选项卡
        notebook = ttk.Notebook(settings_window)
        notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 常规设置选项卡
        general_frame = ttk.Frame(notebook, padding="10")
        notebook.add(general_frame, text="常规")
        
        # 自动创建备份
        auto_backup = tk.BooleanVar(value=True)
        ttk.Checkbutton(general_frame, text="自动创建备份", variable=auto_backup).grid(row=0, column=0, sticky=tk.W, pady=5)
        
        def save_settings():
            settings_window.destroy()
            messagebox.showinfo("成功", "设置已保存")
        
        # 保存按钮
        ttk.Button(settings_window, text="保存", command=save_settings).pack(side=tk.BOTTOM, pady=10)

def verify_password():
    """验证密码窗口"""
    def create_password_window():
        password_window = tk.Toplevel()
        password_window.title("密码验证")
        password_window.geometry("300x150")
        password_window.transient()
        password_window.grab_set()
        
        # 获取屏幕宽度和高度
        screen_width = password_window.winfo_screenwidth()
        screen_height = password_window.winfo_screenheight()
        
        # 计算窗口位置
        window_width = 300
        window_height = 150
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        
        # 设置窗口位置
        password_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
        
        ttk.Label(password_window, text="请输入密码:").pack(pady=20)
        password_entry = ttk.Entry(password_window, show="*")
        password_entry.pack(pady=10)
        
        def check_password():
            # 修复密码计算逻辑
            current_date = datetime.now()
            date_number = int(current_date.strftime("%m%d"))  # 获取月日数字，例如1225
            current_weekday = current_date.isoweekday()  # 获取星期几(1-7)
            correct_password = str(date_number * current_weekday)
            
            if password_entry.get() == correct_password:
                password_window.quit()
                password_window.destroy()
            else:
                messagebox.showerror("错误", "密码错误！请联系作者获取正确密码。")
                password_window.destroy()
                create_password_window()
        
        ttk.Button(password_window, text="确定", command=check_password).pack(pady=10)
        password_entry.bind('<Return>', lambda e: check_password())  # 添加回车键支持
        
        # 处理窗口关闭事件
        password_window.protocol("WM_DELETE_WINDOW", lambda: sys.exit())
        return password_window

    while True:
        pw = create_password_window()
        pw.mainloop()  # 等待密码验证完成
        if not pw.winfo_exists():  # 如果窗口被销毁了（密码正确）
            break

if __name__ == "__main__":
    root = tk.Tk()
    root.withdraw()  # 先隐藏主窗口
    verify_password()  # 验证密码
    root.deiconify()  # 显示主窗口
    app = FileRenamerApp(root)
    root.mainloop()